#include "udp_client.h"

namespace safe_udp
{
    // 向服务器发送文件请求
    void UdpClient::SendFileRequest(const std::string &file_name)
    {
        int n;                       // 用于接收recvfrom的返回值
        int next_seq_expected;       // 预期的下一个序列号
        int segments_in_between = 0; // 预期数据包到已排好序之间的数据段数量
        initial_seq_number_ = 67;    // 初始序列号设置为67

        if (receiver_window_ == 0) // 如果接收窗口未设置
        {
            receiver_window_ = 100; // 设置为默认值100
        }

        // 分配缓冲区
        unsigned char *buffer =
            (unsigned char *)calloc(MAX_PACKET_SIZE, sizeof(unsigned char));

        // 向服务端发送文件请求
        n = sendto(sockfd_, file_name.c_str(), file_name.size(), 0,
                   (struct sockaddr *)&(server_address_), sizeof(struct sockaddr_in));
        // 发送失败
        if (n < 0)
        {
            lg(Info, "客户端发送请求失败");
        }
        memset(buffer, 0, MAX_PACKET_SIZE); // 清空缓冲区

        std::fstream file;                                                 // 文件流对象
        std::string file_path = std::string(CLIENT_FILE_PATH) + file_name; // 构建文件路径
        file.open(file_path.c_str(), std::ios::out);                       // 打开文件准备写入

        // 接收服务端发来的数据,每次（从内核缓冲区）拿1472字节长度的数据上来，n表示收到的总字节数
        while ((n = recvfrom(sockfd_, buffer, MAX_PACKET_SIZE, 0, NULL, NULL)) > 0)
        {
            char buffer2[20];
            memcpy(buffer2, buffer, 20); // 组织数据包前20个字节是否是特定字符串

            // 在一串字符串中查找子字符串
            if (strstr("FILE NOT FOUND", buffer2) != NULL)
            {

                lg(Error, "文件不存在");
                return;
            }

            // 创建一个数据段对象
            std::unique_ptr<DataSegment> data_segment = std::make_unique<DataSegment>();
            // 反序列化数据段
            data_segment->DeserializeToDataSegment(buffer, n);

            // 打印当前数据包的序号

            lg(Info, "当前收到的数据包序号为:%d", data_segment->seq_number_);

            // Random drop随机丢包
            if (is_packet_drop_ && rand() % 100 < prob_value_)
            {

                lg(Info, "丢失了包裹：%d", data_segment->seq_number_);
                continue; // 跳过当前迭代
            }

            // Random delay随机延迟
            if (is_delay_ && rand() % 100 < prob_value_)
            {
                int sleep_time = (rand() % 10) * 1000;
                lg(Info, "延迟了数据包序号:%d，延迟了:%d微秒", data_segment->seq_number_, sleep_time);
                usleep(sleep_time); // 延迟接收
            }

            // 计算预期的下一个序列号，准备排序
            if (last_in_order_packet_ == -1) // 是否接收到任何报文，没有接收到任何报文
            {
                next_seq_expected = initial_seq_number_; // 则预期的下一个序号设置为初始序列号（67）,我希望对面给我发的是序号67
            }
            else // 有接收到任何报文
            {
                // 则预期的下一个报文序号设置为最后一个排好序的报文序号+最后一个报文长度
                // （这个和值是真正表征下一个数据包的序号）
                next_seq_expected = data_segments_[last_in_order_packet_].seq_number_ +
                                    data_segments_[last_in_order_packet_].length_;
            }

            // Old packet检查当前收到的包是否为旧包
            // 如果是，则回应服务端一个ACK，表示我收到了当前数据包（通过序号）
            // 如果当前recvfrom接收到的数据包的序号小于预期的下一个序号（排好序后，真正应该轮到的序号），则认为该数据包为旧包
            // 且不含结束标志位（fin_flag）
            if (next_seq_expected > data_segment->seq_number_ &&
                !data_segment->fin_flag_)
            {
                send_ack(next_seq_expected); // 回应ACK，向服务端回应我收到了
                continue;
            }
            // 如果不是旧包也不是最后一个包，那当前收到的数据包就是新包（序号是否是我期望的下一个，此时不清楚）

            // 计算当前数据包与预期接收的下一个数据包之间应该有多少个数据段
            // 计算公式：
            // （当前数据包序号（一定大于预期的下一个数据包序号） - 预期下一个数据包的序号）/数据段最大长度 = 数据段数量
            // 首先是差值的意思，差值是已排好序的数据包到最新接收到数据包之间的字节数
            // 用这个字节数÷数据段最大长度，就是数据包数量

            segments_in_between =
                (data_segment->seq_number_ - next_seq_expected) / MAX_DATA_SIZE; // 1460字节

            // 计算当前数据包在接收缓冲区中的索引位置，当前数据包应插入到接收缓冲区（vector）的哪个位置
            // 计算公式：当前数据包应插入到接收缓冲区的位置 = 最后一个排好序的数据包索引+数据段数量+1
            // （这里如果不加1的话，指向的序号就是最新插入数据包的前一个序号了，会被覆盖）
            int this_segment_index = last_in_order_packet_ + segments_in_between + 1;

            // 如果当前数据包索引超出了接收窗口大小，则丢弃该数据包
            if (this_segment_index - last_in_order_packet_ > receiver_window_)
            {
                lg(Info, "超过窗口大小，丢弃新来的数据包序号:%d", this_segment_index);
                // Drop the packet, if it exceeds receiver window
                continue;
            }
            // 如果数据包包含结束标志，则记录该状态
            if (data_segment->fin_flag_)
            {
                lg(Info, "最后1个文件数据包已经接收到");
                fin_flag_received_ = true;
            }
            // 将数据包插入到接收缓冲区中的正确位置
            insert(this_segment_index, *data_segment);
            // 遍历接收缓冲区，将连续的数据包写入文件
            for (int i = last_in_order_packet_ + 1; i <= last_packet_received_; i++)
            {
                // 如果数据包的序列号不为-1（表示该位置有有效数据包）
                if (data_segments_[i].seq_number_ != -1)
                {
                    // 如果文件已打开，则将数据包的数据写入文件
                    if (file.is_open())
                    {
                        file << data_segments_[i].data_;
                        // 更新last_in_order_packet_为当前写入的最后一个数据包的索引
                        last_in_order_packet_ = i;
                    }
                }
                else
                {
                    // 如果遇到未接收的数据包（序列号为-1），则停止遍历
                    break;
                }
            }

            // 如果已经接收到 fin_flag_ 且所有数据包都处理完毕，则跳出循环
            // 如果已经接收到结束标志，并且所有数据包都已处理完毕
            // （即last_in_order_packet_等于last_packet_received_），则跳出循环
            if (fin_flag_received_ && last_in_order_packet_ == last_packet_received_)
            {
                break;
            }
            // 发送ACK，确认已接收到的最后一个数据包的序列号加长度
            send_ack(data_segments_[last_in_order_packet_].seq_number_ +
                     data_segments_[last_in_order_packet_].length_);

            memset(buffer, 0, MAX_PACKET_SIZE);
        }

        // 当循环结束后，释放缓冲区并关闭文件
        free(buffer);
        file.close();
    }

    // 创建udp套接字并和服务端连接
    void UdpClient::CreateSocketAndServerConnection(
        const std::string &server_address,
        const std::string &port)
    {
        // 解析服务器地址
        struct hostent *server;
        // 创建服务器地址结构体
        struct sockaddr_in server_address_;
        // 创建socket文件描述符
        int sfd;
        // 将端口号转换为整数
        int port_num;
        port_num = atoi(port.c_str());

        // 创建UDP socket
        sfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (sfd < 0)
        {
            lg(Error, "创建socket套接字失败");
        }

        // 根据服务器地址获取主机信息
        server = gethostbyname(server_address.c_str());
        if (server == NULL)
        {

            lg(Error, "获取服务端主机信息失败");
            exit(0);
        }

        // 初始化服务器地址结构体
        memset(&server_address_, 0, sizeof(server_address_));
        server_address_.sin_family = AF_INET;
        // 复制主机地址到服务器地址结构体
        memcpy(&server_address_.sin_addr.s_addr, server->h_addr, server->h_length);
        // 设置端口号
        server_address_.sin_port = htons(port_num);

        // 保存socket文件描述符
        sockfd_ = sfd;
        // 保存服务器地址结构体
        this->server_address_ = server_address_;
    }

    // 发送确认包，告诉服务器已经成功接收指定序列号的数据包
    void UdpClient::send_ack(int ackNumber)
    {
        lg(Info, "将发送数据包序号:%d", ackNumber);
        int n = 0;
        // 创建数据段对象
        DataSegment *ack_segment = new DataSegment();
        // 确认标志位为true
        ack_segment->ack_flag_ = true;
        // 设置确认序号
        ack_segment->ack_number_ = ackNumber;
        // 设置结束标志位为flase
        ack_segment->fin_flag_ = false;
        // 设置数据长度为0
        ack_segment->length_ = 0;
        // 设置序列号
        ack_segment->seq_number_ = 0;
        // 将数据段序列化为字符数组
        char *data = ack_segment->SerializeToCharArray();
        // 发送ack包到服务器
        n = sendto(sockfd_, data, MAX_PACKET_SIZE, 0,
                   (struct sockaddr *)&(server_address_), sizeof(struct sockaddr_in));
        if (n < 0)
        {
            lg(Info, "发送数据包失败");
        }

        free(data);
    }

    // 将数据段插入到data_segments_向量的指定位置，为了保持数据包的顺序。
    void UdpClient::insert(int index, const DataSegment &data_segment)
    {
        // 如果索引大于最后接收到的数据包索引
        if (index > last_packet_received_)
        {
            // 从最后接收到的数据包索引的下一个位置开始循环，直到当前索引
            for (int i = last_packet_received_ + 1; i <= index; i++)
            {
                // 如果当前索引等于要插入的索引
                if (i == index)
                {
                    // 将数据段插入到数据段向量中
                    data_segments_.push_back(data_segment);
                }
                else
                {
                    // 创建一个空的数据段
                    DataSegment data_segment;
                    // 将空的数据段插入到数据段向量中
                    data_segments_.push_back(data_segment);
                }
            }
            // 更新最后接收到的数据包索引
            last_packet_received_ = index;
        }
        else
        {
            // 如果索引小于等于最后接收到的数据包索引，则直接替换对应位置的数据段
            data_segments_[index] = data_segment;
        }
    }

    // 将新接收的数据段添加到data_segments_向量的末尾。
    int UdpClient::add_to_data_segment_vector(const DataSegment &data_segment)
    {
        // 将数据段添加到数据段向量中
        data_segments_.push_back(data_segment);
        // 返回数据段向量中最后一个元素的索引
        return data_segments_.size() - 1;
    }
}

